using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows.Media.Imaging;
using TakePhotoTogether.Communication;
using TakePhotoTogether.Communication.Protocols;
using TakePhotoTogether.ImageProcessing;
using Windows.Phone.Media.Capture;

namespace TakePhotoTogether
{
    /// <summary>
    /// Class that manages stream operations
    /// </summary>
    public class StreamManager : IRemoteDataListener
    {
        /// <summary>
        /// Action that handles when a image frame is received (After all preprocessing operations)
        /// </summary>
        public Action<MemoryStream> OnFrameReceived;

        /// <summary>
        /// Action that handles when a error occurs on stream operation
        /// </summary>
        public Action OnStreamError;

        /// <summary> Represents the last image frame received from remote partner </summary>
        public StreamImage LastPartnerImage { get; private set; }

        /** Quality Definitions **/
        private const int imageQuality = 50;

        /// <summary> Store the remote view state (Full or Split mode)</summary>
        private ViewState remoteViewState = ViewState.None;

        public bool OrientationChange;

        /// <summary> Worker that handles stream receiving loop control </summary>
        private readonly BackgroundWorker streamSendingWorker = new BackgroundWorker();

        private const int NUMBER_OF_SLICED_PACKETS = 10;

        /// <summary> Stream to be sent to be rendered on UI </summary>
        private readonly MemoryStream frameStream = new MemoryStream();

        private PhotoCaptureDevice camera;
        private readonly SocketManager socketManager;
        private const string DEBUG_TAG = "TakePhotoTogether.StreamManager => ";

        private bool semaphore = false;

        public bool IsNullStreamingMode = false;

        private WriteableBitmap writeable = null;
        private WriteableBitmap croppedWriteable = null;
        private bool firstStreamMode;

        public StreamManager()
        { 
            socketManager = SocketManager.GetSocketManager();

            writeable = new WriteableBitmap(640, 480);
            croppedWriteable = new WriteableBitmap(398, 480);

            // Register which packets will be listening
            socketManager.AddDataListener(this, (int)MessageTypes.StreamPacket);
            socketManager.AddDataListener(this, (int)MessageTypes.ViewStatePacket);

            // Assembly sliced packets
            socketManager.AddDataListener(new SlicedPacketAssembly(), (int)MessageTypes.SlicedPacket);

            // Setup worker that handlers sync loop control
            //streamReceivingWorker.WorkerReportsProgress = false;
            //streamReceivingWorker.WorkerSupportsCancellation = false;
            //streamReceivingWorker.DoWork += new DoWorkEventHandler(processFrameControl);

            // Setup worker that handlers sync loop control
            streamSendingWorker.WorkerReportsProgress = false;
            streamSendingWorker.WorkerSupportsCancellation = true;
            streamSendingWorker.DoWork += new DoWorkEventHandler(StreamSendingLoop);
        }

        /// <summary>
        /// Receive a payload extracted from incoming     packets
        /// </summary>
        /// <param name="data"></param>
        public void ReceiveData(PayloadData data)
        {
            // Avoid null data (Error when sending stream? Should it be treated?)
            if (data == null)
            {
                return;
            }

            // Receives a image frame by means of a StreamData packet
            if (data is StreamData)
            {
                StreamData streamData = (StreamData)data;

                LastPartnerImage = new StreamImage()
                {
                    imageData = streamData.data,
                    width = streamData.width,
                    height = streamData.height
                };

                //Debug.WriteLine("Receiving frame");

                // Outputs a image using 'OnFrameReceived' action
                if (OnFrameReceived != null && !semaphore && streamData.data != null)
                {
                    //Debug.WriteLine("Receiving frame - setting");

                    semaphore = true;

                    frameStream.Seek(0, SeekOrigin.Begin);
                    frameStream.Write(streamData.data, 0, streamData.data.Length);
                    OnFrameReceived(frameStream);
                    GC.SuppressFinalize(streamData);

                    semaphore = false;
                }
                else
                {
                    Debug.WriteLine("Frame discarted!");
                }
            }
            // Receives a packet about change on remote view state (Full or Split)
            else if (data is ViewStateData)
            {
                // Update internal representation of remote view state (Full or Split)
                remoteViewState = ((ViewStateData)data).State;
            }
        }

        public void SetupCamera(PhotoCaptureDevice device)
        {
            camera = device;
        }   

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// 
        private void StreamSendingLoop(object sender, DoWorkEventArgs e)
        {
            Windows.Foundation.Size size;

            try
            {
                size = camera.PreviewResolution;
            }
            catch
            {
                e.Cancel = true;
                return;
            }

            int width = (int)size.Width;
            int height = (int)size.Height;
            MemoryStream stream = new MemoryStream();
            BinaryReader reader = new BinaryReader(stream);
            byte[] imageBytes = null;
            const int streamPacketType = (int)MessageTypes.StreamPacket;
            int croppedWidth = croppedWriteable.PixelWidth;
            int croppedHeight = croppedWriteable.PixelHeight;
            int[] pixels = writeable.Pixels;
            int[] croppedPixels = croppedWriteable.Pixels;
            BackgroundWorker worker = sender as BackgroundWorker;

            try
            {
                while (true)
                {
                    // Check if task was cancelled
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        break;
                    }
                    if (socketManager.GetNumberOfPacketsOnQueue(streamPacketType) == 0 && !worker.CancellationPending)
                    {
                        //if (IsNullStreamingMode)
                        //{
                        //    DataPacketFactory.GetStreamPacket(new byte[] { 0 }, 0, 0).Send();
                        //    Thread.Sleep(500);
                        //}
                        //else
                        //{
                            try
                            {
                                camera.GetPreviewBufferArgb(pixels);
                            }
                            catch
                            {
                                e.Cancel = true;
                                break;
                            }

                            // Check if task was cancelled
                            if (worker.CancellationPending)
                            {
                                e.Cancel = true;
                                break;
                            }

                            if (OrientationChange)
                            {
                                PixelUtil.RotateCropStream(width, height, pixels, croppedWidth, croppedHeight, 121, 0, croppedPixels);
                            }
                            else
                            {
                                PixelUtil.Crop(width, height, pixels, croppedWidth, croppedHeight, 121, 0, croppedPixels);
                            }

                            // Check if task was cancelled
                            if (worker.CancellationPending)
                            {
                                e.Cancel = true;
                                break;
                            }

                            croppedWriteable.SaveJpeg(stream, croppedWidth, croppedHeight, 0, imageQuality);
                            stream.Seek(0, SeekOrigin.Begin);
                            imageBytes = reader.ReadBytes((int)stream.Length);

                            // Check if task was cancelled
                            if (worker.CancellationPending)
                            {
                                e.Cancel = true;
                                break;
                            }

                            if (imageBytes != null)
                            {
                                DataPacket mainPacket = DataPacketFactory.GetStreamPacket(imageBytes, croppedWidth, croppedHeight);
                                mainPacket.SlicingPackets(NUMBER_OF_SLICED_PACKETS).Send();

                                //Debug.WriteLine("StreamSendingLoop - stream queue size: - " + sendingFrameBuffer.Count);
                            }
                            imageBytes = null;
                            stream.Seek(0, SeekOrigin.Begin);
                        }
                    //}

                    // Check if task was cancelled
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        break;
                    }

                    if (firstStreamMode)
                    {
                        firstStreamMode = false;
                        e.Cancel = true;
                        break;
                    }

                    Thread.Sleep(100);
                }
            }
            catch (Exception exception)
            {
                Debug.WriteLine(DEBUG_TAG + "stream error: " + exception.HResult.ToString("X2") + " | " + exception.Message);
                if (OnStreamError != null)
                {
                    OnStreamError();
                }
            }

            stream.Dispose();
            reader.Dispose();
        }

        public void SendFirstStreaming()
        {
            firstStreamMode = true;

            StartSendingStream();
        }

        /// <summary>
        /// Main method to handle stream sending operation.
        /// Should be called just once.
        /// </summary>
        public void StartSendingStream()
        {
            if (!streamSendingWorker.IsBusy)
            {
                streamSendingWorker.RunWorkerAsync();
            }
        }

        public void StopSendingStream()
        {
            if (streamSendingWorker.IsBusy)
            {
                streamSendingWorker.CancelAsync();
            }
        }
    }
}